package oop.ex2.expression;

import java.util.ArrayList;

import oop.ex2.exceptions.EmptyExpression;
import oop.ex2.exceptions.ExpectedAtomicExpression;
import oop.ex2.exceptions.ExpectedBinaryOperation;
import oop.ex2.exceptions.TerminatingStringNotFound;
import oop.ex2.exceptions.TokenNotRecognized;
import oop.ex2.exceptions.UnrecognizedVariable;
import oop.ex2.tokenizer.Tokenizer;

/**
 * This is the base class of all the nodes that represent functions.
 * Each function node has a lower bound on the number of parameters that it can take.
 * In addition, they have a boolean which determines if they can except only that number of
 * parameter, or any larger number.
 * 
 * The function node builds it's children by parsing the expressions in between it's commas until
 * it reaches the end of the function call.
 * @author daniel_i_l
 *
 */
public abstract class FunctionNode extends ExpressionNode {
	private final static NodeType NODE_TYPE = NodeType.ATOM;
	private final static String CURLY_CLOSE = "}";
	private final static String COMMA = ",";

	private boolean _boundedParameterNumber;
	private int _numberOfParameters;
	
	/**
	 * Build a function node.
	 * @param matchedString The string that caused this function to be created.
	 * @param numberOfParameters The minimum number of function arguments.
	 * @param boundedParameterNumber True if the number of arguments is bounded by numberOfParameters
	 * and False otherwise.
	 */
	public FunctionNode(String matchedString, int numberOfParameters, boolean boundedParameterNumber) {
		super(matchedString);
		_nodeType = NODE_TYPE;
		_numberOfParameters = numberOfParameters;
		_boundedParameterNumber = boundedParameterNumber;
	}


	@Override
	public void generateChildren(Tokenizer tokenizer)
			throws TokenNotRecognized, EmptyExpression,
			ExpectedAtomicExpression, ExpectedBinaryOperation,
			TerminatingStringNotFound {
		
		if (tokenizer.isEmpty())
			throw new EmptyExpression("A sequence cannot be empty");
		
		//if there're no parameters then just look for a closing curly bracket.
		if (_numberOfParameters == 0)
		{
			tokenizer.advance(NodeType.SYMBOL); //check for the closing '}'
			ExpressionNode currentNode = tokenizer.getExpressionNode();
			if (currentNode.getMatchedString().equals(CURLY_CLOSE))
				return;
			else
				throw new TerminatingStringNotFound("Expected a closing curly bracket");
		}

		//Otherwise run through the right number of parameters.
		int i=0;
		while (!_boundedParameterNumber || (i < _numberOfParameters))
		{
			SequenceExpressionNode currentNode = new SequenceExpressionNode(true);
			currentNode.addTerminatingString(CURLY_CLOSE);
			
			if (!_boundedParameterNumber || (i < _numberOfParameters - 1))
				currentNode.addTerminatingString(COMMA);
			
			currentNode.generateChildren(tokenizer);
			addChild(currentNode);
			
			if (currentNode.getTerminatedString().equals(CURLY_CLOSE))
				if ( (!_boundedParameterNumber && (i >= _numberOfParameters-1)) ||
						(_boundedParameterNumber && (i == _numberOfParameters-1)) )
					break;
				else
					throw new TerminatingStringNotFound("A curly bracket was encountered when a comma was expected");
			
			i++;
		}
	}

	@Override
	public abstract double calculateNode() throws UnrecognizedVariable;

	/**
	 * Check's if this function can except exactly two parameters as input. This is useful for
	 * determining if the node can be passed to a custom binary operator.
	 * @return True if the function accepts exactly two variables and False otherwise.
	 */
	public boolean isBinaryFunction()
	{
		return (_boundedParameterNumber && (_numberOfParameters==2));
	}
	
	/**
	 * Use this node to evaluate the function on a given list of input arguments.
	 * @param arguments The specified arguments.
	 * @return The output of this function on the given arguments.
	 * @throws UnrecognizedVariable If an unrecognized variable was encountered during the evaluation.
	 */
	public double calculateFunctionFromArgs(ArrayList<Double> arguments) throws UnrecognizedVariable
	{
		//generate constant nodes and put the values into the children.
		_children.clear();
		for (Double num : arguments)
			_children.add(new ConstantNode(num.toString()));
		return calculateNode();
	}

}
